package com.dhms.uikit.media

import android.Manifest.permission.CAMERA
import android.Manifest.permission.RECORD_AUDIO
import android.annotation.SuppressLint
import android.content.Intent
import android.hardware.camera2.CameraCharacteristics
import android.icu.text.SimpleDateFormat
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.DisplayMetrics
import android.util.Log
import android.util.Size
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.*
import androidx.camera.video.VideoCapture
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.dhms.uikit.R
import com.dhms.uikit.databinding.FragmentCameraBinding
import com.dhms.uikit.media.MediaActivity.Companion.FILENAME_FORMAT
import com.dhms.uikit.media.MediaActivity.Companion.PHOTO_EXTENSION
import com.dhms.uikit.media.MediaActivity.Companion.VIDEO_EXTENSION
import com.google.common.util.concurrent.ListenableFuture
import com.permissionx.guolindev.PermissionX
import kotlinx.coroutines.launch
import java.io.File
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

class CameraFragment : Fragment() {

    private var _binding: FragmentCameraBinding? = null
    private val binding get() = _binding!!

    private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
    private var cameraPreview: PreviewView? = null
    private var camera: Camera? = null

    private var cameraSelector: CameraSelector? = null
    private var imagePreview: Preview? = null
    private var cameraProvider: ProcessCameraProvider? = null
    private var imageCapture: ImageCapture? = null

    //    private val cameraCapabilities = mutableListOf<CameraCapability>()
    private var videoCapture: VideoCapture<Recorder>? = null
    private var currentRecording: Recording? = null
    private var recordingState: VideoRecordEvent? = null

    private var cameraExecutor: ExecutorService? = null

    private var isBackCameraSupportMultipleUseCases = true

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentCameraBinding.inflate(inflater, container, false)

        return binding.root
    }

    override fun onDestroy() {
        super.onDestroy()
        tearDownCamera()
        cameraProviderFuture = null

//        val mScaleGestureDetectorField = PreviewView::class.java.getDeclaredField("mScaleGestureDetector")
//        mScaleGestureDetectorField.isAccessible = true
//        mScaleGestureDetectorField.set(cameraPreview, null)
//        val count: Int =  (binding.root as ViewGroup).childCount
//        for (i in count - 1 downTo 0) {
//            val view = (binding.root as ViewGroup).get(i)
//            val contextField = View::class.java.getDeclaredField("mContext")
//            contextField.isAccessible = true
//            contextField.set(view, null)
//        }
//
//        (binding.root as ViewGroup).removeAllViews()
        cameraPreview = null
        camera = null

        cameraSelector = null
        imagePreview?.setSurfaceProvider(null)
        imagePreview = null
        cameraProvider = null
        imageCapture = null

        videoCapture = null
        currentRecording = null
        recordingState = null
        cameraExecutor = null
        videoCaptureListener = null

        binding.cameraLightBtn.setOnClickListener(null)
        binding.cameraBackBtn.setOnClickListener(null)
        binding.cameraCaptureBtn.setOnClickListener(null)
        binding.cameraCaptureBtn.setOnTouchListener(null)
        _binding = null
    }

    @SuppressLint("RestrictedApi")
    private fun tearDownCamera() {
        context?.let {
            val cameraProviderFuture = ProcessCameraProvider.getInstance(it)
            cameraProviderFuture.addListener(Runnable {
                val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                cameraProvider.shutdown()
            }, ContextCompat.getMainExecutor(it))
        }
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.lifecycleOwner = viewLifecycleOwner

        cameraPreview = binding.cameraPreview
        cameraPreview!!.previewStreamState.observe(viewLifecycleOwner) {
            binding.isPreviewStreaming = it == PreviewView.StreamState.STREAMING
        }

        requestPermissions()

        setBackClickListener()

        setSwitchLightStateClickListener()
        setOnCaptureClickListener()
    }

    private fun requestPermissions() = PermissionX.init(this)
        .permissions(arrayListOf(CAMERA, RECORD_AUDIO))
        .request { allGranted, _, _ ->
            if (allGranted) {
                Log.d(TAG, "Camera's permissions passed.")
                startCamera()
            } else {
                Toast.makeText(context, R.string.camera_permissions_tip, Toast.LENGTH_SHORT).show()
                val intent =
                    Intent(MediaActivity.BROADCAST_CREATE_ACTION + (activity as MediaActivity).viewId)
                activity?.sendBroadcast(intent)
                (activity as MediaActivity).finishActivity()
            }
        }

    private fun initCamera() {
        cameraPreview?.implementationMode = PreviewView.ImplementationMode.COMPATIBLE

        cameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
        imagePreview = parsePreviewUseCase()
        cameraProvider = cameraProviderFuture?.get()
        imageCapture = parseImageCaptureCase()

        cameraExecutor = Executors.newSingleThreadExecutor()

        cameraExecutor?.let {
            val recorder =
                Recorder.Builder().setExecutor(it)
                    .setQualitySelector(QualitySelector.from(Quality.SD))
                    .build()
            videoCapture = VideoCapture.withOutput(recorder)
        }
        cameraProvider?.let {
            isBackCameraSupportMultipleUseCases =
                isBackCameraSupportMultipleUseCases(it)
        }
    }

    private fun startCamera() {
        cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
        cameraProviderFuture?.addListener({
            initCamera()
            cameraProvider?.unbindAll()
            try {
                camera = if (isBackCameraSupportMultipleUseCases) {
                    cameraSelector?.let {
                        cameraProvider?.bindToLifecycle(
                            this,
                            it,
                            imagePreview,
                            imageCapture,
                            videoCapture
                        )
                    }
                } else {
                    cameraSelector?.let {
                        cameraProvider?.bindToLifecycle(
                            this,
                            it,
                            imagePreview
                        )
                    }
                }
            } catch (e: Exception) {
                Log.e(TAG, "Use case binding failed", e)
            }
        }, ContextCompat.getMainExecutor(requireContext()))
    }

    private fun parsePreviewUseCase(): Preview {
        return Preview.Builder()
            .setTargetAspectRatio(aspectRatio())
            .setTargetRotation(cameraPreview?.display?.rotation ?: 0)
            .build()
            .also {
                it.setSurfaceProvider(cameraPreview?.surfaceProvider)
            }
    }

    private fun parseImageCaptureCase(): ImageCapture {
        return ImageCapture.Builder().setTargetRotation(cameraPreview?.display?.rotation ?: 0)
            .setTargetResolution(CAMERA_CAPTURE_DEFAULT_SIZE).build()
//            .setTargetAspectRatio(aspectRatio()).build()
    }

    @SuppressLint("ClickableViewAccessibility")
    @RequiresApi(Build.VERSION_CODES.N)
    private fun setOnCaptureClickListener() {
        binding.cameraCaptureBtn.setOnClickListener {
            taskPhoto()
        }

        var isVideoCaptureMode = false
        val handler = Handler(Looper.myLooper()!!)
        val videoRecordTask = Runnable {
            isVideoCaptureMode = true
            startVideoRecord()
        }

        binding.cameraCaptureBtn.setOnTouchListener { _, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    if (!binding.isPreviewStreaming) {
                        Toast.makeText(
                            requireContext(),
                            getString(R.string.preparing_camera),
                            Toast.LENGTH_SHORT
                        ).show()
                        return@setOnTouchListener false
                    }
                    binding.cameraCaptureTip.visibility = View.GONE
                    isVideoCaptureMode = false
                    handler.postDelayed(videoRecordTask, LONG_TOUCH_INTERVAL)
                    false
                }
                MotionEvent.ACTION_UP -> {
                    handler.removeCallbacks(videoRecordTask)
                    if (!binding.isPreviewStreaming) {
                        return@setOnTouchListener true
                    }
                    if (!isVideoCaptureMode) {
                        taskPhoto()
                    } else {
                        stopVideoRecord()
                    }
                    true
                }
                MotionEvent.ACTION_MOVE -> {
                    true
                }
                else -> false
            }
        }
    }

    private fun taskPhoto() {
        if (!isBackCameraSupportMultipleUseCases) {
            cameraProvider?.unbindAll()
            cameraSelector?.let {
                cameraProvider?.bindToLifecycle(
                    viewLifecycleOwner,
                    it,
                    imagePreview,
                    imageCapture
                )
            }
        }
        startTaskPhotoAnimation()

        val photoFile = createFile(
            (activity as MediaActivity).outputDirectory,
            FILENAME_FORMAT,
            PHOTO_EXTENSION
        )
        val outputFileOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        cameraExecutor?.let {
            imageCapture?.takePicture(outputFileOptions, it,
                object : ImageCapture.OnImageSavedCallback {
                    override fun onError(error: ImageCaptureException) {
                        Log.e(TAG, "Failed to capture a photo: $error")
                    }

                    override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                        val savedUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
                        Log.d(TAG, "Capture a photo in ${outputFileResults.savedUri}")
                        navigateToPreviewFragment(savedUri, MediaType.PHOTO)
                    }
                })
        }
    }

    @SuppressLint("MissingPermission")
    private fun startVideoRecord() {
        if (currentRecording != null) {
            return
        }

        if (!isBackCameraSupportMultipleUseCases) {
            cameraProvider?.unbindAll()
            cameraSelector?.let {
                cameraProvider?.bindToLifecycle(
                    viewLifecycleOwner,
                    it,
                    imagePreview,
                    videoCapture
                )
            }
        }

//        val contentValues = ContentValues().apply {
//            put(
//                MediaStore.Video.Media.DISPLAY_NAME,
//                Date().toFormatDate(FILENAME_FORMAT) + VIDEO_EXTENSION
//            )
//            put(
//                MediaStore.Video.Media.MIME_TYPE,
//                "video/$VIDEO_EXTENSION"
//            )
////            put(
////                MediaStore.MediaColumns.RELATIVE_PATH,
////                Environment.DIRECTORY_DCIM + File.separator + "${getString(R.string.dhms)}/videos"
////            )
//        }
//        val outputFileOptions = MediaStoreOutputOptions.Builder(
//            requireActivity().contentResolver,
//            MediaStore.Video.Media.EXTERNAL_CONTENT_URI
//        ).setContentValues(contentValues).build()

        val videoFile = createFile(
            (activity as MediaActivity).outputDirectory,
            FILENAME_FORMAT,
            VIDEO_EXTENSION
        )
        val outputFileOptions = FileOutputOptions.Builder(videoFile).build()

        currentRecording = cameraExecutor?.let {
            videoCaptureListener?.let { it1 ->
                videoCapture?.output?.prepareRecording(requireActivity(), outputFileOptions)
                    ?.apply { withAudioEnabled() }
                    ?.start(it, it1)
            }
        }

        binding.cameraVideoCaptureTimeoutView.visibility = View.VISIBLE
        binding.cameraVideoCaptureTimeoutView.start()

        Log.d(TAG, "Start recording video.")
    }

    private fun stopVideoRecord() {
        binding.cameraVideoCaptureTimeoutView.stop()
        binding.cameraVideoCaptureTimeoutView.visibility = View.GONE
        currentRecording?.stop()
        currentRecording = null
    }

    /**
     * VideoCaptureEvent listener.
     */
    private var videoCaptureListener: Consumer<VideoRecordEvent>? = Consumer<VideoRecordEvent>
    { event ->
        // cache the recording state
        if (event !is VideoRecordEvent.Status)
            recordingState = event

        if (event is VideoRecordEvent.Finalize) {
            recordingState = event
            Log.d(TAG, event.outputResults.outputUri.path.toString())
            // display the captured video
            navigateToPreviewFragment(event.outputResults.outputUri, MediaType.VIDEO)
        }
    }

    private fun startTaskPhotoAnimation() {
        val anim = AnimationUtils.loadAnimation(context, R.anim.take_photo)
        anim.setAnimationListener(object : Animation.AnimationListener {
            override fun onAnimationStart(p0: Animation?) {}
            override fun onAnimationEnd(p0: Animation?) {}
            override fun onAnimationRepeat(p0: Animation?) {}
        })
        binding.cameraCaptureBtn.startAnimation(anim)
    }

    private fun setSwitchLightStateClickListener() {
        binding.cameraLightBtn.apply {
            setCompoundDrawablesWithIntrinsicBounds(
                R.drawable.ic_light_on, 0, 0, 0
            )
            setOnClickListener {
                if (camera?.cameraInfo?.torchState?.value == TorchState.ON) {
                    camera?.cameraControl?.enableTorch(false)
                    setCompoundDrawablesWithIntrinsicBounds(
                        R.drawable.ic_light_on, 0, 0, 0
                    )
                } else {
                    camera?.cameraControl?.enableTorch(true)
                    setCompoundDrawablesWithIntrinsicBounds(
                        R.drawable.ic_light_off, 0, 0, 0
                    )
                }
            }
        }
    }

    private fun aspectRatio(): Int {
        val metrics = DisplayMetrics().also { cameraPreview?.display?.getRealMetrics(it) }
        val width = metrics.widthPixels
        val height = metrics.heightPixels

        val previewRatio = max(width, height).toDouble() / min(width, height)
        val deltaFrom4To3 = abs(previewRatio - RATIO_4_3_VALUE)
        val deltaFrom16To9 = abs(previewRatio - RATIO_16_9_VALUE)

        if (deltaFrom4To3 <= deltaFrom16To9) {
            return AspectRatio.RATIO_4_3
        }
        return AspectRatio.RATIO_16_9
    }

    private fun setBackClickListener() {
        binding.cameraBackBtn.setOnClickListener {
            val intent =
                Intent(MediaActivity.BROADCAST_CREATE_ACTION + (activity as MediaActivity).viewId)
            activity?.sendBroadcast(intent)
            (activity as MediaActivity).finishActivity()
        }
    }

    private fun navigateToPreviewFragment(fileUri: Uri, fileType: MediaType) {
        (activity as MediaActivity).setCurrentMediaItem(
            MediaItem(
                fileType, fileUri
            )
        )
        lifecycleScope.launch {
            findNavController().navigate(R.id.action_cameraFragment_to_previewFragment)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // 权限拒绝会出现未初始化的情况
        if (this::cameraExecutor != null) {
            cameraExecutor?.shutdown()
        }
    }

    @androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
    fun isBackCameraSupportMultipleUseCases(cameraProvider: ProcessCameraProvider): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cameraHardwareLevel = CameraSelector.DEFAULT_BACK_CAMERA
                .filter(cameraProvider.availableCameraInfos)
                .firstOrNull()
                ?.let { Camera2CameraInfo.from(it) }
                ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
                ?: return false
            return cameraHardwareLevel in SUPPORTED_MULTIPLE_USE_CASES_CAMERA_HARDWARE_LEVEL
        }
        return false
    }


    companion object {
        private const val RATIO_4_3_VALUE = 4.0 / 3.0
        private const val RATIO_16_9_VALUE = 16.0 / 9.0

        private val CAMERA_CAPTURE_DEFAULT_SIZE = Size(1080, 1920)

        private val SUPPORTED_MULTIPLE_USE_CASES_CAMERA_HARDWARE_LEVEL = listOf(
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3,
        )

        private const val TAG = "CameraFragment"

        private const val LONG_TOUCH_INTERVAL = 200L

        private fun createFile(baseFolder: File, format: String, extension: String) =
            File(
                baseFolder, SimpleDateFormat(format, Locale.CHINA)
                    .format(System.currentTimeMillis()) + extension
            )
    }
}